import logging

import io.ioloop
import comm_id

log = logging.getLogger('api')

#------------------------------------------------------------------------------
class Api:
    """Defines the upcalls and downcalls that the clients connecting
    to this service are going to use."""

    class ConnectFactory(io.ioloop.ListenFactory):
        """Handles creating channels upon connection for API clients"""

        def __init__(self, api):
            self.api_ = api
        
        def make_handle(self, sock):
            log.info('Api socket opened')
            a = ApiSockHandle(self.api_, sock)
            if self.api_.client_:
                self.api_.client_.close()
            self.api_.client_ = a

            # send all the channels that are currently open to the
	    # connected client
            peers = self.api_.tx_.get_peers()
            for k in peers.keys():
                peers[k].resignal_reqs()

	    # XXX/bowei -- make me better
	    self.api_.resend_recvd_data()
            return a
    
    def __init__(self, ap, tx):
        self.ap_ = ap
        self.tx_ = tx

	# XXX/bowei -- temporary measure for now, we maintain a small
	# queue for received data
	self.recvd_data_ = []

        if self.ap_[0] == 'stdin':
            log.info('using stdin for IPC')
            self.server_sock_ = None
            self.client_ = ApiSockHandle(self, io.ioloop.StdinSockAdapter())
            io.ioloop.master_loop.add_handle(self.client_)
        elif self.ap_[1] != 0:
            log.info('using TCP for IPC')
            self.server_sock_ = io.ioloop.ListenHandle(self.ap_,
                                                    Api.ConnectFactory(self))
            self.client_ = None
            io.ioloop.master_loop.add_handle(self.server_sock_)
        else:
            log.info('using UNIX domain for IPC')
            self.server_sock_ = io.ioloop.UnixListenHandle(self.ap_[0],
                                                           Api.ConnectFactory(self))
            self.client_ = None
            io.ioloop.master_loop.add_handle(self.server_sock_)            

    # tx -> client messages
    def send_handshake_req(self, comm_id):
        """Send a handshake request to the client"""
	if self.client_:
	    self.client_.send_frame('h %s' % comm_id)
	else:
	    log.debug('No client: handshake_req: h %s' % comm_id)

    def send_data_req(self, comm_id):
        """Send a request for data from the client"""
	if self.client_:
	    self.client_.send_frame('d %s' % comm_id)
	else:
            log.debug('No client: data_req: d %s')

    def send_disconnect_hint(self, comm_id):
        """Send a disconnection hint to cancel a data request"""
	if self.client_:
	    self.client_.send_frame('f %s' % comm_id)
	else:
            log.debug('No client: disconnect_hint: f %s' % comm_id)

    def send_handshake_recvd(self, comm_id, hs):
        """Send a received handshake"""
	if self.client_:
	    self.client_.send_frame('H %s %s' % (comm_id, hs))
	else:
            log.debug('No client: handshake_recvd: H %s %s' %
                      (comm_id, hs))
    
    def send_data_recvd(self, comm_id, data):
        """Send a received data message"""
	if self.client_:
	    self.client_.send_frame('D %s %s' % (comm_id, data))
	else:
	    self.recvd_data_.append((comm_id, data))
	    if len(self.recvd_data_) > 100:
		log.warn('Overflow: %d/100 msgs in the recvd queue' %
                         len(self.recvd_data_))
            log.debug('No client: send_data_recvd: D %s %s' %
                      (comm_id, data))
    
    def resend_recvd_data(self):
	for d in self.recvd_data_:
	    self.client_.send_frame('D %s %s' % d)
	self.recvd_data_ = []

    # client -> tx daemon messages
    def process_api_msg(self, msg):
        try:
            if len(msg) < 1:
                raise ValueError()
            if msg[0] == 'h':
                self.handshake_req_resp(msg)
            elif msg[0] == 'q':
                self.quit_tx()
            elif msg[0] == 'd':
                self.data_req_resp(msg)
            else:
                raise ValueError()
        except ValueError:
            if self.ap_[0] == 'stdin':
                log.warn('Client(stdin) is sending garbage "%s", ignoring' %
                         msg)
            else:
                log.error('Client sent garbage "%s", closing...' % msg)
                self.client_.close()            

    def handshake_req_resp(self, msg):
        """Client response to handshake request"""
        _, c_id, hs = msg.split(' ', 2)
        assert _ == 'h'
        c_id = comm_id.CommId.from_str(c_id)
        comm = self.tx_.get_comm_by_id(c_id)
        if comm:
            comm.send_handshake(hs)
        else:
            log.warn('Trying to send hs to %s, but doesn\'t exist' % c_id)
        
    def data_req_resp(self, msg):
        """Client response to data request"""
        _, c_id, data = msg.split(' ', 2)
        assert _ == 'd'
        c_id = comm_id.CommId.from_str(c_id)
        comm = self.tx_.get_comm_by_id(c_id)
        if comm:
            comm.send_data(data)
        else:
            log.warn("Trying send data to %s, but doesn't exist" % cid)

    def quit_tx(self):
        self.tx_.quit()

#------------------------------------------------------------------------------
class ApiSockHandle(io.ioloop.SockHandle):
    """Handles the protocol interactions with the client"""

    def __init__(self, api, sock):
        io.ioloop.SockHandle.__init__(self, sock)
        self.api_ = api

    def __repr__(self):
        return '<ApiSockHandle %s>' % self.sock_
        
    def recv_frame(self, msg): 
        """Virtual method from SockHandle"""
        self.api_.process_api_msg(msg)
            
    def close(self):
        io.ioloop.SockHandle.close(self)
        self.api_.client_ = None
        log.info('Api socket closed')
